package org.msb.accentis.gui.views;

import static java.awt.GridBagConstraints.BOTH;
import static java.awt.GridBagConstraints.CENTER;
import static java.awt.GridBagConstraints.EAST;
import static java.awt.GridBagConstraints.NONE;
import static java.awt.GridBagConstraints.WEST;

import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;

import org.msb.accentis.data.AccountType;
import org.msb.accentis.data.CurrencyDecimal;
import org.msb.accentis.data.DataManagerFactory;
import org.msb.accentis.data.IDataManager;
import org.msb.accentis.data.IDataManagerFactoryListener;
import org.msb.accentis.data.IDataManagerListener;
import org.msb.accentis.data.pojo.Account;
import org.msb.accentis.gui.InsetsConstants;
import org.msb.accentis.gui.Resources;
import org.msb.accentis.gui.managedialog.ManageAccountDialogPage;
import org.msb.accentis.gui.managedialog.ManagePojoDialog;
import org.msb.accentis.gui.swing.ActionResourceDefinition;
import org.msb.accentis.gui.swing.CurrencyDecimalTableCellRenderer;
import org.msb.accentis.gui.swing.MSBAction;

public class AccountsView extends AbstractView {

	private JTable table;

	private ActionListener actionListener;

	private Map<String, Action> actions = new HashMap<String, Action>();

	private ManagePojoDialog managePojoDialog;

	private ManageAccountDialogPage dialogPage;

	public AccountsView(ManagePojoDialog dialog) {
		super("accounts");

		managePojoDialog = dialog;
	}

	public void configureAction(Action action) {
		ActionResourceDefinition ard = new ActionResourceDefinition(Resources.getInstance().getString("action.view.accounts"), null);
		ard.configureAction(action);
	}

	public JComponent getViewComponent() {
		JComponent component = new JPanel();

		component.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
		component.setLayout(new GridBagLayout());

		component.add(new JLabel(Resources.getInstance().getString("accountsview.title.caption")), new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, WEST, NONE,
				InsetsConstants.ZERO_INSETS, 0, 0));

		component.add(new JScrollPane(getTable()), new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, CENTER, BOTH, InsetsConstants.FIVE_ON_TOP_INSETS, 0, 0));

		component.add(createButtonPanel(), new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, EAST, NONE, InsetsConstants.FIVE_ON_TOP_INSETS, 0, 0));

		return component;
	}

	protected JTable getTable() {
		if (null == table)
			createTable();

		return table;
	}

	private void createTable() {
		table = new JTable(new AccountTableModel());
		table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
			@Override
			public void valueChanged(ListSelectionEvent e) {
				getAction("button.modify").setEnabled(1 == table.getSelectedRowCount());
				getAction("button.remove").setEnabled(1 <= table.getSelectedRowCount());
			}
		});
		table.setDefaultRenderer(CurrencyDecimal.class, new CurrencyDecimalTableCellRenderer());
	}

	private JPanel createButtonPanel() {
		JPanel panel = new JPanel(new GridLayout(1, 3, 5, 0));

		panel.add(createButton(getAction("button.create"), true));
		panel.add(createButton(getAction("button.modify"), false));
		panel.add(createButton(getAction("button.remove"), false));

		return panel;
	}

	private JButton createButton(Action action, boolean enabled) {
		action.setEnabled(enabled);

		return new JButton(action);
	}

	protected Action getAction(String actionName) {
		Action action = actions.get(actionName);
		if (null == action)
			action = createAction(actionName);

		return action;
	}

	private Action createAction(String actionName) {
		Action action = new MSBAction(new ActionResourceDefinition(Resources.getInstance().getString("action." + actionName), actionName), getActionListener());
		actions.put(actionName, action);

		return action;
	}

	protected ActionListener getActionListener() {
		if (null == actionListener)
			createActionListener();

		return actionListener;
	}

	private void createActionListener() {
		actionListener = new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				String command = e.getActionCommand();
				assert null != command : "null action command encountered in AccountsView action listener.";

				if (command.equals("button.create"))
					doCreate();
				else if (command.equals("button.modify"))
					doModify();
				else if (command.equals("button.remove"))
					doRemove();
			}
		};
	}

	private ManagePojoDialog getManagePojoDialog() {
		return managePojoDialog;
	}

	private ManageAccountDialogPage getManageAccountDialogPage() {
		if (null == dialogPage)
			dialogPage = new ManageAccountDialogPage(getManagePojoDialog());

		return dialogPage;
	}

	private void doCreate() {
		getManageAccountDialogPage().createPojo();
	}

	private void doModify() {
		Account account = ((AccountTableModel) getTable().getModel()).getAccountAt(getTable().getSelectedRow());
		getManageAccountDialogPage().editPojo(account);
	}

	private void doRemove() {
		int[] rows = getTable().getSelectedRows();
		((AccountTableModel) getTable().getModel()).removeAccounts(rows);
	}

	static class AccountTableModel extends AbstractTableModel implements IDataManagerFactoryListener {

		private static String[] COLUMN_NAMES = { "Name", "Number", "Type", "Balance" };

		private static Class<?>[] COLUMN_CLASSES = { String.class, String.class, AccountType.class, CurrencyDecimal.class };

		private List<Account> accounts = new ArrayList<Account>();

		AccountTableModel() {
			DataManagerFactory.getInstance().addDataManagerFactoryListener(this);
		}

		@Override
		public int getRowCount() {
			return accounts.size();
		}

		@Override
		public int getColumnCount() {
			return COLUMN_NAMES.length;
		}

		@Override
		public String getColumnName(int column) {
			return COLUMN_NAMES[column];
		}

		@Override
		public Class<?> getColumnClass(int column) {
			return COLUMN_CLASSES[column];
		}

		@Override
		public Object getValueAt(int row, int column) {
			Account account = getAccountAt(row);

			switch (column) {
			case 0:
				return account.getName();
			case 1:
				return account.getNumber();
			case 2:
				return account.getAccountType();
			case 3:
				return new CurrencyDecimal(account.getStartingBalance(), account.getCurrency());
			}

			return null;
		}

		protected Account getAccountAt(int row) {
			return accounts.get(row);
		}

		protected void removeAccounts(int[] rows) {
			Arrays.sort(rows);

			for (int i = rows.length - 1; i >= 0; --i)
				DataManagerFactory.getInstance().getDataManager().remove(accounts.get(rows[i]));
		}

		@Override
		public void dataManagerChanged(IDataManager dataManager) {
			accounts.clear();
			accounts.addAll(dataManager.<Account> runNamedQuery("Account.getAllAccounts"));
			fireTableDataChanged();

			dataManager.addDataManagerListener(getAccountDataManagerListener(), Account.class);
		}

		private IDataManagerListener<Account> getAccountDataManagerListener() {
			return new IDataManagerListener<Account>() {
				@Override
				public void objectCreated(Account object) {
					int index = Collections.binarySearch(accounts, object);
					if (0 <= index)
						return;

					index = -index - 1;
					accounts.add(index, object);
					fireTableRowsInserted(index, index);
				}

				@Override
				public void objectRemoved(Account object) {
					int index = Collections.binarySearch(accounts, object);
					if (0 > index)
						return;

					accounts.remove(index);
					fireTableRowsDeleted(index, index);
				}

				@Override
				public void objectSaved(Account object) {
					int preSortIndex = accounts.indexOf(object);
					Collections.sort(accounts);
					int postSortIndex = Collections.binarySearch(accounts, object);

					if (preSortIndex != postSortIndex) {
						fireTableRowsDeleted(preSortIndex, preSortIndex);

						if (postSortIndex > preSortIndex)
							postSortIndex--; // Account for the smaller row index having been removed.

						fireTableRowsInserted(postSortIndex, postSortIndex);
					} else
						fireTableRowsUpdated(preSortIndex, preSortIndex);
				}

				@Override
				public void objectRefreshed(Account object) {
				}
			};
		}
	}
}
